<?php
/**
 * @package mikrokosmos
 * mk script file created on 15-giu-2005
 * jaco_at_pixeldump_dot_org
 */

if(!class_exists("MKTagHeader")){ class MKTagHeader {}}

/**
 * doAction tag class wrapper
 */
class MKActionScriptContainer extends MKTagHeader {

	var $swfHeader;
	var $tagData = "";
	var $arObj;
	var $flasmDir = SERVER_MKCODE;
	var $swfDir = SERVER_MKFLASH;
	var $errorLog = "";
	var $flasmData = "";
	var $swfFileName = "";
	var $flasmFileName = "";
	var $workingFrame = 1;

	//var $constants = array();

	// the constructor
	function MKActionScriptContainer($tagData = "", $workingFrame = 1, $flagDisassemble = false){

		$this->workingFrame = $workingFrame;
		$this->arObj = new MKActionRecords();

		if(strlen($tagData)){
			$this->tagData = $tagData;
			parent::MKTagHeader(substr($tagData, 0, 10));
			if($flagDisassemble) $this->disassemble();
		}
	}

	//
	function set_arObj($arObj) {$this->arObj = $arObj; }
	function get_arObj() { return $this->arObj; }

	//
	function set_flasmDir($flasmDir = SERVER_MKCODE){
		$this->flasmDir = $flasmDir;
	}

	function get_flasmDir() { return $this->flasmDir; }

	//
	function add_actionRecord($actionRecord){
		$this->arObj->add_actionRecord($actionRecord);
	}

	//
	function insert_actionRecordAt($ar, $idx){
		$this->arObj->insert_actionRecordAt($ar, $idx);
	}

	//
	function add_instruction($instruction = ""){

		if(strlen($instruction)){

			$flasmBlock = array();

			if(strpos($instruction, "\r\n") !== false){
				$flasmBlock = preg_split("/\r\n/", trim($instruction));
			}
			else{
				$flasmBlock[] = "  " .$instruction;
			}

			$this->assemble_flasm_statements($flasmBlock);
		}
	}

	//
	function reset_data(){
		$this->tagData = "";
		$this->arObj = new MKActionRecords();
		$this->flasmDir = SERVER_MKCODE;
		$this->errorLog = "";
	}

	function assemble_from_file($flmFN, $frameRequest = 1){

		$flmPath = $this->flasmDir .$flmFN;
		$this->flasmFileName = $flmFN;

		if(!file_exists($flmPath)) {
			$this->cast_error("error reading file, check path or flm file name");
			return false;
		}

		$this->arObj = new MKActionRecords();

		$frameStr = "" .($frameRequest - 1);
		$src = file($flmPath);

		$flasmHeader = read_flasm_header($src[0]);
		$frameCount = $flasmHeader["frameCount"];

		if($frameCount < $frameRequest - 1){
			$this->cast_error("requested frame does not exists");
			return false;
		}

		$flasmBlock = isolate_flasm_block($src, "frame", $frameStr);

		if(count($flasmBlock) <= 2){
			$this->cast_error("requested frame is empty");
			return false;
		}

		$this->assemble_flasm_statements($flasmBlock);

		return $this->assemble();
	}

	//
	function assemble_flasm_statements($flasmBlock){

		$fs = get_flasm_statements($flasmBlock);
		$fbCount = count($fs["tokens"]);

		for($i = 0; $i < $fbCount; $i++){ // skip frame/end_frame statement declaration

			$fsToken = $fs["tokens"][$i];

			// skip pseudo statement such as:
			// frame, label, end
			if($fsToken["totalLength"]){
				$ar = new MKActionRecord("", $fsToken);
				$this->arObj->add_actionRecord($ar);
			}
		}
	}

	//
	function disassemble_from_file($swfFN, $frameRequest = 1){

		$this->swfFileName = $swfFN;
		$this->workingFrame = $frameRequest;

		$si = new MKSwfInspector($swfFN, $this->swfDir);
		$swfVersion = $si->get_swfVersion();
		$siAr = $si->fetch_assoc();

		$this->swfHeader = new MKSwfHeader($swfFN,
											$swfVersion,
											$siAr["stageWidth"],
											$siAr["stageHeight"],
											$siAr["fps"],
											$siAr["frameCount"],
											$siAr["backgroundColor"],
											$siAr["isCompressed"]);

		if($swfVersion >= 8)
			$this->swfHeader->set_metadata($si->get_metadata());

		$mt = $si->fetch_assoc();
		$fl = $si->get_frameList();
		//$sca = new MKSwfSerializer($mt, $fl);
		$im = new MKSwfItemGrabber($swfFN, "", $frameRequest, ACTIONS);

		$asTag = $im->get_rawTagAt(0);

		$this->tagData = $asTag->get_tagData();
		parent::MKTagHeader(substr($this->tagData, 0, 10));

		$this->disassemble();
	}

	//
	function assemble(){

		$tBody = $this->arObj->assemble();
		$this->tagData = SWFTag_long_header(ACTIONS, strlen($tBody)) .$tBody;

		return $this->tagData;
	}

	// extraOffset is needed by MKInitActionContainer class
	function disassemble($extraOffset = 0){

		$atBody = substr($this->tagData, $this->tagSL + $extraOffset);
		$this->arObj = new MKActionRecords($atBody);
	}

	function cast_error($error){
		$this->errorLog .= $error ."\r\n";
	}

	function get_errorLog() { return $this->errorLog; }

	//
	function fetch_assoc(){
		/* TODO */
	}

	//
	function fetch_xml($includeHeader = true, $tabLevel = 0){
		/* TODO */
	}

	//
	function fetch_flasm_header(){

		$fStr = "movie '";
		$fStr .= $this->swfDir . $this->swfFileName;
		$fStr .= "' // flash ";
		$fStr .= $this->swfHeader->get_swfVersion();
		$fStr .= ", total frames: " .$this->swfHeader->get_frameCount();
		$fStr .= ", frame rate: " .$this->swfHeader->get_fps();
		$fStr .= " fps, " .$this->swfHeader->get_stageWidth() ."x";
		$fStr .= $this->swfHeader->get_stageHeight() ." px";

		return $fStr;
	}

	//
	function fetch_flasm($includeHeader  = true, $ilStart = 0, $includeWorkingFrame = true){

		$il = $ilStart;
		$fStr = "";
		$wf = $this->workingFrame - 1; // working frame, flasm starts framecount from 0

		if($includeHeader) $fStr = $this->fetch_flasm_header();


		if($includeWorkingFrame){
			$il += 2;	// indentation level
			$ilStr = str_repeat(" ", $il);
			$fStr .= "\r\n" .$ilStr ."frame " .$wf;
		}

		$fStr .= $this->arObj->fetch_flasm($il + 4);

		if($includeWorkingFrame)
			$fStr .= $ilStr ."end // of frame " .$wf;

		if($includeHeader) $fStr .= "\r\nend";

		$fStr .= "\r\n";

		return $fStr;
	}
}

/**
 * initclip
 * supported from swf6
 */
class MKInitActionContainer extends MKActionScriptContainer {

	var $spriteID = 1;

	// the constructor
	function MKInitActionContainer($tagData = "", $workingFrame = 1, $flagDisassemble = false){

		$this->workingFrame = $workingFrame;
		$this->tagData = $tagData;

		parent::MKActionScriptContainer($tagData, $workingFrame, $flagDisassemble);

		if(strlen($tagData)){
			parent::MKTagHeader(substr($tagData, 0, 10));
			$this->spriteID = ui16dec(substr($tagData, $this->tagSL, 2));
			if($flagDisassemble) $this->disassemble();
		}
	}


	//
	function set_spriteID($spriteID) {$this->spriteID = $spriteID; }
	function get_spriteID() { return $this->spriteID; }

	//
	function disassemble(){
		parent::disassemble(2);
	}

	//
	function fetch_flasm($includeHeader  = true, $ilStart = 0){

		$asStr = parent::fetch_flasm(false, 2, false);

		$fStr = "\r\n  initMovieClip " .$this->spriteID;
		$fStr .= $asStr;
		$fStr .= "  end // of initMovieClip ";
		$fStr .= $this->spriteID;
		$fStr .= "\r\n";

		return $fStr;
	}

	//
	function assemble(){

		$tBody = $this->arObj->assemble();
		$this->tagData = SWFTag_long_header(INITCLIP, strlen($tBody)) .$tBody;

		return $this->tagData;
	}
}

/**
 * clip actions
 */
class MKClipActionsContainer {

	var $buf = "";
	var $ceFlags = 0;
	var $caRecords = array();
	var $managedEvents = array();
	var $swfVersion = 6;			// this is mandatory to manage clipeventflags


	function MKClipActionsContainer(	$buf = "",
										$caRecords = array(),
										$swfVersion = -1,
										$flagDisassemble = false){

		if($swfVersion == -1) $this->swfVersion = get_global_swfVersion();
		else $this->swfVersion = $swfVersion;

		if(strlen($buf)){

			$this->buf = $buf;
			$this->get_clipActions_data();

			if($flagDisassemble) $this->disassemble();

			return;
		}

		if(count($caRecords)) $this->caRecords = $caRecords;
	}

	//
	function set_buf($buf) { $this->buf = $buf; }
	function get_buf() { return $this->buf; }

	//
	function set_caRecords($caRecords) { $this->caRecords = $caRecords; }
	function get_caRecords() { return $this->caRecords; }

	//
	function set_swfVersion($swfVersion) {$this->swfVersion = $swfVersion; }
	function get_swfVersion() { return $this->swfVersion; }

	//
	function set_clipActions_data($cad = array()){
	}

	function get_clipActions_data($cad = array()){

		$ceLength = $this->swfVersion >= 6 ? 4 : 2;

		if(!count($cad)){
			$cad = read_clipActions_header_data(substr($this->buf, 0, $ceLength + 2));
		}

		$this->ceFlags = $cad["ceFlags"];
		$this->managedEvents = $cad["managedEvents"];
		$ceLength = $this->swfVersion >= 6 ? 4 : 2;
		$endOffset = strlen($this->buf) - 2 - $ceLength * 2;
		$caBuf = substr($this->buf, $ceLength + 2, $endOffset);
		$caLength = strlen($caBuf);
		$ofst = 0;
		$k = 0;

		do {

			$caidx = count($this->caRecords);
			$car = new MKClipActionRecord(substr($caBuf, $ofst), $this->swfVersion);
			$car->set_offset($ofst);
			$this->caRecords[$caidx] = $car;
			$ofst += $car->get_totalLength();
			$k++;

		} while($ofst < $caLength);
	}

	//
	function fetch_assoc(){
		/* TODO */
	}

	function fetch_xml($includeHeader = true, $tabLevel = 0, $nodeName = "clipActions"){

		$xmlStr = $includeHeader ? XML_HEADER : "";
		$pfx = get_xml_indent_row(++$tabLevel);

		$xmlStr .= $pfx ."<" .$nodeName ." ";
		$xmlStr .= "managedEvents=\"" .implode(", ", $this->managedEvents) ."\"";
		$xmlStr .= ">";

		$xmlStr .= hex_dump($this->buf, true);

		/* TODO fill data */
		$xmlStr .= $pfx ."</" .$nodeName .">";

		return $xmlStr;
	}

	// note that caBuf must be cleaned from end clipActionRecord
	function disassemble(){
		foreach($this->caRecords as $ca) $ca->disassemble;
	}

	function assemble(){

		$buf = swf_word(0);		// always starts with ui16 0x0000
		/* TODO: implement clipActions container */
	}

	//
	function fetch_flasm($indentationLevel = 4){

		if(!strlen($this->buf)) return "";

		if(!count($this->caRecords)) $this->disassemble();

		$il = $indentationLevel;
		$ilStr = str_repeat($il, " ");

		$fStr = "";

		foreach($this->caRecords as $ca){
			$fStr .= $ca->fetch_flasm($il);
		}
		//$fStr .= "\r\n";

		return $fStr;
	}
}

/**
 * ACTIONRECORD
 */
class MKActionRecord {

	var $buf = "";			//
	var $actionCode;		//
	var $actionName;		//
	var $codeSize;			// used by function actionrecord
	var $functionName = ""; //
	var $functionFlags = array();
	var $withBlock = "";
	var $tryFlags = array();
	var $catchInName = "";
	var $catchInRegister = 0;
	var $trySize;
	var $catchSize;
	var $finallySize;

	var $totalLength = 1;		// length of actionrecord, as stored in swf
	var $params = array();	//
	var $offset = 0;

	function MKActionRecord($buf = "", $arData = array()){

		if(strlen($buf)){
			$this->buf = $buf;
			$this->get_actionRecord_data();
		}
		else{
			$arCount = count($arData);

			if($arCount){

				if(isset($arData["actionCode"])) $this->actionCode = $arData["actionCode"];
				if(isset($arData["actionName"])) $this->actionName = $arData["actionName"];

				if($this->actionCode <= 0) $this->actionCode = get_actionCode($arData["actionName"]);

				if(isset($arData["totalLength"])) $this->totalLength = $arData["totalLength"];
				else if($this->actionCode < 0x80) $this->totalLength = 1;

				if(isset($arData["params"])){
					$this->params = $arData["params"];
				}

				if(isset($arData["offset"])) $this->offset = $arData["offset"];
				if(isset($arData["codeSize"])) $this->codeSize = $arData["codeSize"];
				if(isset($arData["functionName"])) $this->functionName = $arData["functionName"];
				if(isset($arData["functionFlags"])) $this->functionFlags = $arData["functionFlags"];
				if(isset($arData["withBlock"])) $this->withBlock = $arData["withBlock"];
				if(isset($arData["tryFlags"])) $this->tryFlags = $arData["tryFlags"];
				if(isset($arData["catchInName"])) $this->catchInName = $arData["catchInName"];
				if(isset($arData["catchInRegister"])) $this->catchInRegister = $arData["catchInRegister"];
				if(isset($arData["catchSize"])) $this->catchSize = $arData["catchSize"];
				if(isset($arData["finallySize"])) $this->finallySize = $arData["finallySize"];
			}
		}
	}

	//
	function set_buf($buf) { $this->buf = $buf; }
	function get_buf() { return $this->buf; }

	//
	function set_actionCode($actionCode) {
		$this->actionCode = $actionCode;
		if($this->actionCode < 0x80) $this->length = 1;
	}
	function get_actionCode() { return $this->actionCode; }

	//
	function set_actionName($actionName) { $this->actionName = $actionName; }
	function get_actionName() { return $this->actionName; }

	//
	function set_totalLength($totalLength) { $this->totalLength = $totalLength; }

	function get_totalLength() {
		if($this->actionCode < 0x80) return 1;
		return $this->totalLength;
	}

	//
	function set_offset($offset) {$this->offset = $offset; }
	function get_offset() { return $this->offset; }

	//
	function set_params($params) {
		$this->params = array();
		$this->params = $params;
	}
	function get_params() { return $this->params; }

	//
	function get_actionRecord_data($ard = array()){

		if(!count($ard)){
			$ard = array();
			$ard = read_actionRecord_data($this->buf);
		}

		$this->actionCode = $ard["actionCode"];
		$this->actionName = $ard["actionName"];

		if(isset($ard["totalLength"])) $this->totalLength = $ard["totalLength"];
		if(isset($ard["params"])) $this->params = $ard["params"];

		if(isset($ard["offset"])) $this->offset = $ard["offset"];
		if(isset($ard["codeSize"])) $this->codeSize = $ard["codeSize"];
		if(isset($ard["functionName"])) $this->functionName = $ard["functionName"];
		if(isset($ard["functionFlags"])) $this->functionFlags = $ard["functionFlags"];

		if(isset($ard["tryFlags"])) $this->tryFlags = $ard["tryFlags"];
		if(isset($ard["catchInName"])) $this->catchInName = $ard["catchInName"];
		if(isset($ard["catchInRegister"])) $this->catchInRegister = $ard["catchInRegister"];
		if(isset($ard["trySize"])) $this->trySize = $ard["trySize"];
		if(isset($ard["catchSize"])) $this->catchSize = $ard["catchSize"];
		if(isset($ard["finallySize"])) $this->finallySize = $ard["finallySize"];

		$this->buf = substr($this->buf, 0, $this->totalLength);
	}

	function set_actionRecord_data($ard = array()){

		if(count($ard)){
			$this->get_actionRecord_data($ard);
		}
		else {
			$ard = array();
			$ard = $this->fetch_assoc();
		}

		if($ard["actionName"] == "add"){
			$ard["actionName"] = "add2";
			$ard["actionCode"] = get_actionCode("add2");
		}
		$this->buf = write_actionRecord_data($ard);

		return $this->buf;
	}

	// an alias of set_actionRecord_data
	function update_buf(){
		//if(strlen($this->buf)) return $this->buf;
		return $this->set_actionRecord_data();
	}

	//
	function fetch_assoc(){

		$ard = array();
		$ard["actionCode"] = $this->actionCode;
		$ard["actionName"] = $this->actionName;
		$ard["offset"] = $this->offset;
		$ard["totalLength"] = $this->totalLength;

		if(count($this->params)) $ard["params"] = $this->params;
		if(strlen($this->functionName)) $ard["functionName"] = $this->functionName;
		if(count($this->functionFlags)) $ard["functionFlags"] = $this->functionFlags;

		$blocks = array("function", "function2", "with", "try");
		if(in_array($this->actionName, $blocks, true))
			$ard["codeSize"] = $this->codeSize;

		if($this->actionName == "try"){
			$ard["catchInRegister"] = $this->catchInRegister;
			$ard["catchInName"] = $this->catchInName;
			$ard["trySize"] = $this->trySize;
			$ard["catchSize"] = $this->catchSize;
			$ard["finallySize"] = $this->finallySize;
		}

		return $ard;
	}

	//
	function fetch_xml($includeHeader = true, $tabLevel = 0, $nodeName = "action"){

		$xmlStr = $includeHeader ? XML_HEADER : "";

		$pfx = get_xml_indent_row(++$tabLevel);
		$xmlStr .= $pfx ."<" .$nodeName ." ";

		$xmlStr .= "actionName=\"" .$this->actionName ."\" >";

		/* TODO parameters list */

		$pfx = get_xml_indent_row(--$tabLevel);
		$xmlStr .= $pfx ."</" .$nodeName .">";

		return $xmlStr;
	}

	//
	function get_flasm_statement($constants = array()){

		$fs = "";
		$actionName = $this->actionName;
		if($actionName == "add2") $actionName = "add";
		else if(strpos($actionName, "lessThan") !== false) $actionName = "lessThan";

		$fs .= $actionName;
		$pCount = count($this->params);

		if($actionName == "function"){

			if(strlen($this->functionName)) $fs .= " " .$this->functionName;

			$fs .= " (";

			if($pCount){
				$pStr = "'";
				foreach($this->params as $p){
					$pStr .= $p["value"] . "', ";
				}
				$pStr = substr($pStr, 0, strlen($pStr) - 2);
				$fs .= $pStr;
			}

			$fs .= ")";
		}
		else if($actionName == "function2"){

			if(strlen($this->functionName)) $fs .= " " .$this->functionName;

			$fs .= " (";

			if($pCount){
				$pStr = "";
				foreach($this->params as $p){

					if($p["code"] == AS_REGISTER)
						$pStr .= "r:" .$p["index"] ."=";

					$pStr .= "'" .$p["value"] . "', ";
				}
				$pStr = substr($pStr, 0, strlen($pStr) - 2);
				$fs .= $pStr;
			}

			$fs .= ") (";

			$fr = "";

			$ffSequence = array();
			$ffSequence["this"] = "this";
			$ffSequence["arguments"] = "arguments";
			$ffSequence["super"] = "super";
			$ffSequence["root"] = "_root";
			$ffSequence["parent"] = "_parent";
			$ffSequence["global"] = "_global";

			$ffPresence = array();

			foreach($this->functionFlags as $k => $v){

				if($v && strpos($k, "preload") !== false)
					$ffPresence[] = strtolower(substr($k, 7));
			}

			$ffCount = count($ffPresence);

			for($i = 0; $i < $ffCount; $i++){

				$ffStr = $ffPresence[$i];

				$fr .= "r:" .($i + 1) ."='";
				$fr .= $ffSequence[$ffStr] ."', ";
			}

			$fs .= substr($fr, 0, strlen($fr) - 2);


			$fs .= ")";
		}
		else if($actionName == "with"){
			if(strlen($this->withBlock)){
				$fs .= " '" .$this->withBlock ."'";
			}
		}
		else if($actionName == "try"){

			if(strlen($this->catchInName)){
				$fs .= $this->catchInName;
			}
			else $fs .= " r:" .$this->catchInRegister;
		}
		else if($actionName == "end"){
			if(count($this->params)){
				$fs .= " // of ";
				$fs .= $this->params[0]["value"];
				$fs .= (isset($this->params[1])) ? " " .$this->params[1]["value"] : "";
			}
		}
		else if($actionName == "label"){
			$fs .= $this->params[0]["value"] .":";
		}
		else if(strpos($actionName, "branch") !== false){
			$fs .= " label";
			$fs .= $this->params[0]["value"];
		}
		else if($actionName == "setRegister"){
			$fs .= " r:";
			$fs .= $this->params[0]["value"];
		}
		else if($actionName == "push"){

			$cCount = count($constants);

			foreach($this->params as $p){
				if($p["code"] == AS_STRING){
					$fs .= " '" .$p["value"] ."',";
				}
				else if($p["code"] == AS_FP){
					$fs .= " " .$p["value"] .",";
				}
				else if($p["code"] == AS_UNDEFINED){
					$fs .= " UNDEF,";
				}
				else if($p["code"] == AS_REGISTER){
					$fs .= " r:" .$p["value"] .",";
				}
				else if($p["code"] == AS_BOOL){
					$booleans = array("FALSE", "TRUE");
					$fs .= " " .$booleans[$p["value"]] .",";
				}
				else if($p["code"] == AS_DOUBLE){
					$fs .= " " .$p["value"] .",";
				}
				else if($p["code"] == AS_INT){
					$fs .= " " .$p["value"] .",";
				}
				else if($p["code"] == AS_CONST8){

					$fs .= " ";

					if($cCount){
						$fs .= "'" .$constants[$p["value"]] ."'";
					}
					else {
					 	$fs .= "c:" .$p["value"];
					}
					$fs .= ",";
				}
				else if($p["code"] == AS_CONST16){
					$fs .= " ";

					if($cCount){
						$fs .= "'" .$constants[$p["value"]] ."'";
					}
					else {
					 	$fs .= "c:" .$p["value"];
					}
					$fs .= ",";
				}
				else {
					$fs .= " " .$p["value"] .",";
				}
			}
			$fs = substr($fs, 0, strlen($fs) - 1);
		}
		else if($pCount){

			foreach($this->params as $p){

				$delim = ($p["code"] == AS_STRING) ? "'" : "";
				$fs .= " " .$delim .$p["value"] .$delim .",";
			}

			$fs = substr($fs, 0, strlen($fs) - 1);
		}


		return $fs;
	}

	// alias of get_flasm_statement
	function fetch_flasm(){
		return $this->get_flasm_statement();
	}
}


/**
 * CLIPACTIONRECORD
 */
class MKClipActionRecord {

	var $buf = "";
	var $eventFlags = 0;			// clipEvent flags
	var $caSize = 0;				// clipAction size
	var $keyCode = 0;				// keyCode if applicable
	var $offset = -1;				// offset position in tag data
	var $totalLength = 0;			// speaks itself
	var $arObj;						// body of clipAction, a collection of actionRecords
	var $swfVersion = 6;			// mandatory to manage clipEvent flags

	var $managedEvents = array();
	var $constants = array();

	function MKClipActionRecord($buf = "", $swfVersion = 6, $card = array()){

		$this->buf = $buf;
		$this->swfVersion = $swfVersion;
		$this->arObj = new MKActionRecords();
		$ceLength = $this->swfVersion >= 6 ? 4 : 2;

		if(strlen($buf)){

			/*
			$this->eventFlags = read_clipEventFlags_data(substr($this->buf, 0, $ceLength), $this->swfVersion);
			$this->caSize = ui32dec(substr($this->buf, $ceLength, 4));
			$this->totalLength = $this->caSize + $ceLength + 4;
			*/

			$this->get_clipActionRecord_data();
		}
		else if(count($card))$this->get_clipActionRecord_data($card);
	}

	//
	function set_buf($buf) { $this->buf = $buf; }
	function get_buf() { return $this->buf; }

	//
	function set_eventFlags($eventFlags) { $this->eventFlags = $eventFlags; }
	function get_eventFlags() { return $this->eventFlags; }

	//
	function set_caSize($caSize) { $this->caSize = $caSize; }
	function get_caSize() { return $this->caSize; }

	//
	function set_keyCode($keyCode) { $this->keyCode = $keyCode; }
	function get_keyCode() { return $this->keyCode; }

	//
	function set_offset($offset) { $this->offset = $offset; }
	function get_offset() { return $this->offset; }

	//
	function set_totalLength($totalLength) { $this->totalLength = $totalLength; }
	function get_totalLength() { return $this->totalLength; }

	//
	function set_arObj($arObj) {$this->arObj = $arObj; }
	function get_arObj() { return $this->arObj; }

	//
	function set_swfVersion($swfVersion) { $this->swfVersion = $swfVersion; }
	function get_swfVersion() { return $this->swfVersion; }

	//
	function set_clipActionRecord_data($card = array()){

		if(!count($card))
			$card = $this->fetch_assoc();

		$this->buf = write_clipActionRecord_data($card);

		return $this->buf;
	}

	//
	function get_clipActionRecord_data($card = array()){

		if(!count($card))
			$card = read_clipActionRecord_data($this->buf, $this->swfVersion);

		$this->eventFlags = $card["ceFlags"];
		$this->managedEvents = $card["managedEvents"];
		$this->caSize = $card["caSize"];
		$this->totalLength = $card["totalLength"];
		$this->buf = substr($this->buf, 0, $this->totalLength);

		if(isset($card["keyCode"])) $this->keyCode = $card["keyCode"];
		$this->arObj = new MKActionRecords($card["caBuf"]);
	}


	// alias of set_clipActionRecord_data
	function update_buf(){
		return $this->set_clipActionRecord_data();
	}

	//
	function fetch_assoc(){
		/* TODO: implement fetch_assoc */

		$card = array();
		//$this->

		return $card;
	}

	//
	function fetch_flasm($indentationLevel = 4){

		if(!strlen($this->buf)) return "";

		$il = $indentationLevel;	// indentation level
		$ilStr = str_repeat(" ", $il);

		$fStr = "\r\n" .$ilStr ."onClipEvent ";
		$fStr .= implode(" ", $this->managedEvents);
		$fStr .= $this->arObj->fetch_flasm($il + 2);
		$fStr .= "\r\n" .$ilStr ."end";
		$fStr .= "\r\n";

		return $fStr;
	}

	//
	function fetch_xml($includeHeader = true, $tabLevel = 0, $nodeName = "clipActionRecord"){

		$xmlStr = $includeHeader ? XML_HEADER : "";

		/* TODO: implement fetch_xml */

		return $xmlStr;
	}

	//
	function insert_actionRecordAt($ar, $idx){
		$this->arObj->insert_actionRecordAt($ar, $idx);
	}

	//
	function add_instruction($instruction = ""){

		if(strlen($instruction)){

			$flasmBlock = array();

			if(strpos($instruction, "\r\n") !== false){
				$flasmBlock = preg_split("/\r\n/", $instruction);
			}
			else{
				$flasmBlock[] = "  " .$instruction;
			}

			$this->assemble_flasm_statements($flasmBlock);
		}
	}


	//
	function assemble(){
		/* TODO */
	}

	//
	function disassemble(){

		$this->arObj->disassemble();
		/*
		if(!strlen($caBuf)){
			if(!strlen($this->buf)) return false;

			$ceLength = $this->swfVersion >= 6 ? 4 : 2;

			if(!$this->totalLength)
				$this->totalLength = ui32dec(substr($this->buf, $ceLength, 4));

			if(strlen($this->buf) > $this->totalLength)
				$this->buf = substr($this->buf, 0, $this->totalLength);

			$caBuf = substr($this->buf, $ceLength + 4);
		}

		$this->arObj = new MKActionRecords($caBuf, array(), true);
		*/
	}
}		// end of MKClipActionRecord


/**
 * actionRecords block
 * it is commonly used by various mk actionscript classes
 */
class MKActionRecords {

	var $buf = "";
	var $actionRecords = array();
	var $errorLog = "";
	var $constants = array();

	// the constructor
	function MKActionRecords($buf = "", $actionRecords = array(), $flagDisassemble = false){

		$this->buf = $buf;

		if(strlen($this->buf) && $flagDisassemble) $this->disassemble();
	}

	//
	function add_actionRecord($caRecord){
		$this->actionRecords[] = $caRecord;
	}

	//
	function insert_actionRecordAt($ar, $idx){

		$sl1 = array_slice($this->actionRecords, 0, $idx);
		$sl1[] = $ar;
		$sl2 = array_slice($this->actionRecords, $idx);
		$this->actionRecords = array_merge($sl1, $sl2);
	}

	//
	function add_instruction($instruction = ""){

		if(strlen($instruction)){

			$flasmBlock = array();

			if(strpos($instruction, "\r\n") !== false){
				$flasmBlock = preg_split("/\r\n/", $instruction);
			}
			else{
				$flasmBlock[] = "  " .$instruction;
			}

			$this->assemble_flasm_statements($flasmBlock);
		}
	}

	//
	function assemble(){

		$this->buf = "";

		foreach($this->actionRecords as $action){

			$actionName = $action->get_actionName();

			if($actionName == "label" || $actionName == "end") continue;

			$this->buf .= $action->update_buf();
		}

		$this->buf .= NIL; // append end actions

		return $this->buf;
	}

	//
	function disassemble(){

		$atLength = strlen($this->buf);
		if(!$atLength) return false;

		$ofst = 0;
		$k = 0;

		$labels = array();
		$fes = array();
		$src = array();

		$needComputeLabel = false;
		$needComputeFunction = true;
		$blocks = array("function", "function2", "with", "try");

		do {
			$aidx = count($this->actionRecords);
			$ar = new MKActionRecord(substr($this->buf, $ofst));
			$ar->set_offset($ofst);

			if($ar->get_actionName() == "constants"){
				$params = $ar->get_params();
				foreach($params as $p) $this->constants[] = $p["value"];
			}

			$actionName = $ar->get_ActionName();
			$this->actionRecords[$aidx] = $ar;
			$src[$k] = $ar->fetch_assoc();

			if(strpos($actionName, "branch") !== false)	$needComputeLabel = true;
			else if(in_array($actionName, $blocks, true)) $needComputeFunction = true;

			$ofst += $ar->get_totalLength();
			$k++;
		} while($ofst < $atLength);

		if($needComputeLabel){

			$jumpReport = compute_label_branch($src);

			foreach($jumpReport["branches"] as $branch){
				$params = $branch["params"];
				$idx = $branch["index"];
				$this->actionRecords[$idx]->set_params($params);
				$labels = $jumpReport["labels"];
			}
		}

		if($needComputeFunction) $fes = compute_function_ends($src);

		// pseudo statements insertion
		$ps = array_merge($labels, $fes);

		$ps = sort_flasm_pseudoStatements($ps);

		$psCount = count($ps);
		$k = 0;

		for($i = 0; $i < $psCount; $i++){
			$fs = $ps[$i];
			$idx = $fs["index"] + $k;
			$ar = new MKActionRecord("", $fs);
			$this->insert_actionRecordAt($ar, $idx);
			$k++;
		}
	}

	//
	function fetch_flasm($ilStart = 2){

		if(!count($this->actionRecords)) $this->disassemble();
		if(!count($this->actionRecords)) return "";

		$il = $ilStart;
		$fStr = "";
		$arCount = count($this->actionRecords);
		$blocks = array("function", "function2", "with", "try");
		$cf = array("catch", "finally");

		if($arCount){

			for($i = 0; $i < $arCount; $i++){

				$ilStr = str_repeat(" ", $il);
				$ar = $this->actionRecords[$i];
				$actionName = $ar->get_actionName();

				if($actionName == "endFlag") break;

				if($actionName == "label") $ilStr = str_repeat(" ", $il - 1);
				if($actionName == "push") $fs = $ar->get_flasm_statement($this->constants);
				else $fs = $ar->get_flasm_statement();

				$bl = "";

				if($actionName == "end"){
					$il -= 2;
					$ilStr = str_repeat(" ", $il);
					$bl = "\r\n";
				}

				if(in_array($actionName, $cf, true)) $ilStr = substr($ilStr, 0, -2);
				$fStr .= "\r\n" .$ilStr .$fs .$bl;

				if(in_array($actionName, $blocks, true)){
					$il += 2;
					$ilStr = str_repeat(" ", $il);
				}
			}

			$il = $ilStart + 2;
			$ilStr = str_repeat(" ", $il);
		}

		$fStr .= "\r\n";

		return $fStr;
	}
}		// end of MKActionRecords

// ----------- end of mk classes - actionscript --------
?>
